﻿namespace eCommerce.BusinessLogic
{
    public enum CardType
    {
        MasterCard, BankCard, Visa, AmericanExpress, Discover, DinersClub, JCB
    };

    public class CardValidator
    {
        private CardValidator() { } // static only

        /// <summary>
        /// Validates the specified card type.
        /// </summary>
        /// <param name="cardType">Type of the card.</param>
        /// <param name="cardNumber">The card number.</param>
        /// <returns></returns>
        public static bool Validate(CardType cardType, string cardNumber)
        {
            // Remove any whitespace
            cardNumber = cardNumber.Replace(" ", "");

            byte[] number = new byte[16]; // number to validate

            // Remove non-digits
            int len = 0;
            for (int i = 0; i < cardNumber.Length; i++)
            {
                if (char.IsDigit(cardNumber, i))
                {
                    if (len == 16) return false; // number has too many digits
                    number[len++] = byte.Parse(cardNumber[i].ToString());
                }
            }

            // Validate based on card type, first if tests length, second tests prefix
            switch (cardType)
            {
                case CardType.MasterCard:
                    if (len != 16)
                        return false;
                    if (number[0] != 5 || number[1] == 0 || number[1] > 5)
                        return false;
                    break;

                case CardType.BankCard:
                    if (len != 16)
                        return false;
                    if (number[0] != 5 || number[1] != 6 || number[2] > 1)
                        return false;
                    break;

                case CardType.Visa:
                    if (len != 16 && len != 13)
                        return false;
                    if (number[0] != 4)
                        return false;
                    break;

                case CardType.AmericanExpress:
                    if (len != 15)
                        return false;
                    if (number[0] != 3 || (number[1] != 4 && number[1] != 7))
                        return false;
                    break;

                case CardType.Discover:
                    if (len != 16)
                        return false;
                    if (number[0] != 6 || number[1] != 0 || number[2] != 1 || number[3] != 1)
                        return false;
                    break;

                case CardType.DinersClub:
                    if (len != 14)
                        return false;
                    if (number[0] != 3 || (number[1] != 0 && number[1] != 6 && number[1] != 8)
                        || number[1] == 0 && number[2] > 5)
                        return false;
                    break;

                case CardType.JCB:
                    if (len != 16 && len != 15)
                        return false;
                    if (number[0] != 3 || number[1] != 5)
                        return false;
                    break;
            }

            // Use Luhn Algorithm to validate
            return IsValidNumber(cardNumber);
        }

        /// <summary>
        /// Determines whether [is valid number] [the specified number].
        /// </summary>
        /// <param name="number">The number.</param>
        /// <returns>
        /// 	<c>true</c> if [is valid number] [the specified number]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsValidNumber(string number)
        {
            int[] deltas = new[] { 0, 1, 2, 3, 4, -4, -3, -2, -1, 0 };
            int checksum = 0;
            char[] chars = number.ToCharArray();
            for (int i = chars.Length - 1; i > -1; i--)
            {
                int j = chars[i] - 48;
                checksum += j;
                if (((i - chars.Length) % 2) == 0)
                    checksum += deltas[j];
            }

            return ((checksum % 10) == 0);
        }
    }
}

